Skip to content

feat(meshcore): channel create/edit/delete UI + source-type-aware routes (phase 3/3)#3039

Merged
Yeraze merged 1 commit into
mainfrom
feat/meshcore-channels-phase-3
May 16, 2026
Merged

feat(meshcore): channel create/edit/delete UI + source-type-aware routes (phase 3/3)#3039
Yeraze merged 1 commit into
mainfrom
feat/meshcore-channels-phase-3

Conversation

@Yeraze
Copy link
Copy Markdown
Owner

@Yeraze Yeraze commented May 16, 2026

Phase 3 of 3 for MeshCore channels support — closes the feature. See docs/meshcore-channels-plan.md.

Summary

Adds a per-source Configuration UI for creating, editing, regenerating-secret, and deleting channels on a MeshCore device, plus source-type-aware backend routes that route writes through meshcoreManager.setChannel/deleteChannel for MeshCore sources.

What changed

Backend

  • PUT /api/channels/:id is now source-type-aware:
    • MeshCore: drops the 0..7 cap (firmware allows arbitrary indices), widens the name cap to 31 bytes, decodes the base64 PSK to a 16-byte secret, calls meshcoreManager.setChannel(idx, name, secretHex). Rejects non-16-byte secrets with 400; surfaces device-side failures as 502.
    • Meshtastic: unchanged (existing setChannelConfig admin-message path).
  • DELETE /api/channels/:id similarly drops the 0..7 cap and the "cannot delete primary" gate for MeshCore (it has no primary concept), routing through meshcoreManager.deleteChannel(idx).
  • Source type is resolved via databaseService.sources.getSource(); legacy NULL sourceId is treated as Meshtastic.

Frontend

  • New MeshCoreChannelsConfigSection.tsx mounted inside MeshCoreConfigurationView. Provides:
    • Channel list scoped to the current source.
    • Add channel button (auto-assigns the lowest free idx, seeds a crypto.getRandomValues 16-byte secret).
    • Edit / Regenerate secret / Delete per row.
    • Secret displayed as hex with masked-by-default show/copy toggles, matching the Meshtastic PSK UX.
  • MeshCoreConfigurationView now accepts baseUrl + sourceId so the channels sub-section can hit the API.

Why a separate config component, not a unified one

The Meshtastic ChannelsConfigSection is 937 LOC built around drag-reorder of a fixed 8-slot grid plus role/uplink/downlink/positionPrecision — none of which apply to MeshCore. Forking keeps both readable. The shared backend layer (channels repository, write/delete routes) is already source-type-aware.

Test plan

  • npx vitest run4950 PASS / 0 FAIL (+7 over main)
  • npm run build — clean
  • tsc --noEmit — clean
  • Lint error count unchanged from main (650 pre-existing)
  • Manual: connect a MeshCore Companion device, navigate to Configuration, verify Add/Edit/Regenerate/Delete operations propagate to the device and back to the UI.

New tests (+7 frontend)

MeshCoreChannelsConfigSection.test.tsx:

  • Renders each channel from /api/channels/all?sourceId=...
  • Empty-state when API returns []
  • Permission gating disables Edit/Delete/Add when canWrite=false
  • "Add channel" seeds next-free idx + generates a 16-byte secret (mocked deterministic)
  • Save sends PUT /api/channels/<idx> with base64 PSK + sourceId, then re-fetches
  • Delete sends DELETE /api/channels/<idx>?sourceId=<src> with confirm + re-fetch
  • Secret input is type=password by default, toggles to text on Show

Why no server-route integration tests

server.ts route handlers aren't factored into a mountable router that supertest can wrap. Phase 1's manager-level tests (mocking nativeBackend.sendCommand) already exercise the MeshCore write/delete paths the route delegates to; the route layer is a thin source-type discriminator + base64↔hex conversion.

Plan completion

This PR closes the three-phase MeshCore channels feature:

🤖 Generated with Claude Code

…tes (phase 3/3)

Closes the MeshCore channels feature with a per-source Configuration UI
and source-type-aware backend routes.

Backend
- PUT /api/channels/:id is now source-type-aware. For MeshCore sources
  the channelId is no longer capped at 0-7 (MeshCore allows arbitrary
  device-dependent indices), the name length cap is widened to 31
  bytes, and the write path goes through
  `meshcoreManager.setChannel(idx, name, secretHex)` after decoding
  the base64 PSK to its 16-byte secret. A non-16-byte secret is
  rejected with 400; a device-side failure returns 502.
- DELETE /api/channels/:id similarly drops the 0-7 cap for MeshCore,
  removes the "cannot delete primary channel" gate (MeshCore has no
  primary), and routes the delete through
  `meshcoreManager.deleteChannel(idx)`.
- Source type is resolved via `databaseService.sources.getSource()`
  with a Meshtastic-default fallback for the legacy NULL sourceId case.

Frontend
- New `MeshCoreChannelsConfigSection.tsx` (not a unification of the
  Meshtastic ChannelsConfigSection — that component is 900+ LOC of
  drag-reorder + role/uplink/downlink semantics that don't apply
  here). Provides add / edit / regenerate-secret / delete, hex secret
  display with show/copy toggle, and a "+ Add channel" that
  auto-assigns the lowest free idx and seeds a `crypto.getRandomValues`
  16-byte secret.
- Mounted inside `MeshCoreConfigurationView`, which now receives
  `baseUrl` and `sourceId` from `MeshCorePage` for the API surface.

Tests (+7 frontend)
- MeshCoreChannelsConfigSection.test.tsx: list rendering, empty state,
  permission gating, "Add channel" idx auto-assignment + secret
  generation, Save sends correct PUT body (base64 PSK + sourceId),
  Delete sends correct DELETE URL, secret-visibility toggle.

Server-route integration tests are intentionally skipped: server.ts
handlers aren't factored into a mountable router for supertest, and
phase 1's manager-level tests already cover the MeshCore write/delete
paths the route delegates to.

Closes the 3-phase MeshCore channels feature (docs/meshcore-channels-plan.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 16, 2026

Claude finished @Yeraze's task —— View job


PR Review Complete ✅

This is a well-implemented completion of the MeshCore channel configuration feature. The code follows project conventions and demonstrates good security practices. Here's my detailed analysis:


Code Quality & Best Practices

Backend Route Changes (src/server/server.ts:2803-3034)

  • Source-type awareness: Excellent implementation of conditional logic based on sourceType resolution
  • Validation layering: Proper progressive validation (ID format → source type → permissions → data format)
  • Error handling: Comprehensive error responses with appropriate HTTP status codes (400, 403, 502)
  • Base64↔hex conversion: Correct handling of PSK format differences between frontend (hex) and backend (base64)

Frontend Components

  • Clean separation: Smart decision to fork rather than complicate the existing Meshtastic channels UI
  • React patterns: Proper use of hooks, memoization, and state management
  • TypeScript: Strong typing throughout with clear interfaces
  • Accessibility: Good use of ARIA attributes and semantic HTML

Security Analysis

PSK/Secret Handling

  • ✅ Secrets are masked by default with show/hide toggle (line 105, 320)
  • ✅ 16-byte AES-128 validation enforced on both frontend and backend
  • crypto.getRandomValues() used for secure secret generation
  • ✅ Permission gating at both route and UI levels
  • ✅ Base64 encoding prevents binary data issues in JSON transport

Authentication & Authorization

  • ✅ Per-channel write permissions enforced (channel_${channelId} resources)
  • ✅ Source-scoped operations prevent cross-source data leakage
  • ✅ Frontend disables controls when canWrite=false or missing permissions

Architecture Compliance

Multi-source Architecture

  • ✅ All operations scoped by sourceId parameter
  • ✅ Source type resolution via databaseService.sources.getSource()
  • ✅ Proper use of resolveSourceManager() for device communication
  • ✅ No global singleton usage - follows per-source manager pattern

Database Layer

  • ✅ No raw SQL - proper use of repository pattern
  • ✅ Channel operations properly scoped with sourceId
  • ✅ Consistent with existing channel CRUD methods

Performance Considerations

Frontend Optimizations

  • ✅ Memoized calculations (nextFreeIdx, presetId)
  • ✅ Debounced state updates with proper dependency arrays
  • ✅ Cancellable async operations to prevent race conditions
  • ✅ Efficient re-render patterns with targeted state updates

Backend Efficiency

  • ✅ Early validation to fail fast on invalid requests
  • ✅ Single DB lookups for source type resolution
  • ✅ Appropriate use of async/await patterns

Test Coverage

New Tests (+7 frontend tests in MeshCoreChannelsConfigSection.test.tsx)

  • List rendering: Tests channel display and API integration
  • Permission gating: Verifies UI disables when canWrite=false
  • Add channel: Tests next-free-index logic and secret generation
  • Save operation: Validates PUT request format and re-fetch behavior
  • Delete operation: Tests DELETE request and confirmation flow
  • Secret visibility: Confirms masked-by-default behavior
  • Deterministic mocking: Good use of crypto.getRandomValues mock

Test Quality

  • ✅ Proper mocking of external dependencies
  • ✅ Realistic API response simulation
  • ✅ Both success and error path coverage
  • ✅ Clear test descriptions and assertions

🔍 Minor Observations

Validation Consistency (server.ts:2837-2845)

  • The name length validation correctly uses different limits (11 for Meshtastic, 31 for MeshCore) but consider extracting these constants to a shared location for maintainability.

Error Recovery (server.ts:2917-2922)

  • Good practice: MeshCore write failures return 502 (Bad Gateway) rather than 500, correctly indicating the external device is the failure point.

UI/UX (MeshCoreChannelsConfigSection.tsx:334-363)

  • The inline "Add channel" editor UX is intuitive - shows the new channel being added before save.

📝 Recommendations

  1. Constants: Consider moving SECRET_BYTES = 16 and MAX_NAME_BYTES = 31 to a shared constants file since they're referenced in validation logic.

  2. Error Messages: The error messages are comprehensive, but consider adding error codes for easier debugging (following the pattern used elsewhere in the codebase).

  3. Future Enhancement: The hex input validation could be enhanced with real-time feedback as the user types.


🎯 Summary

This PR successfully completes the 3-phase MeshCore channels implementation with:

  • Robust security through proper validation and permission enforcement
  • Excellent code quality with clear separation of concerns
  • Strong adherence to the project's multi-source architecture
  • Comprehensive testing of the new functionality
  • Good UX with intuitive channel management interface

The implementation demonstrates thorough understanding of the codebase patterns and security requirements. The decision to fork the UI rather than extend the existing Meshtastic component was wise given the fundamental differences in channel semantics.

Ready for merge


Job run | Branch: feat/meshcore-channels-phase-3

@Yeraze Yeraze merged commit df06f8e into main May 16, 2026
17 checks passed
@github-actions
Copy link
Copy Markdown

System Test Results

MeshMonitor System Test Results

Test Run: 2026-05-15 20:43:30 EDT

Test Summary

Test Suite Result
Configuration Import ✅ PASSED
Quick Start Test ✅ PASSED
Security Test ✅ PASSED
V1 API Test ✅ PASSED
Reverse Proxy Test ✅ PASSED
Reverse Proxy + OIDC ✅ PASSED
Virtual Node CLI Test ✅ PASSED
Backup & Restore Test ✅ PASSED
Database Migration Test ✅ PASSED
DB Backing Consistency ✅ PASSED
API Exercise (3 DBs) ✅ PASSED

✅ Overall Result: PASSED

All deployment configurations are working correctly!

Test Details

Configuration Import:

  • Tests configuration import and device reboot cycle
  • Verifies channel roles, PSKs, and LoRa configuration
  • Note: Channel name verification skipped due to architectural limitation

Quick Start Test:

  • Zero-config deployment (no SESSION_SECRET or COOKIE_SECURE required)
  • HTTP access without HSTS
  • Auto-generated admin user with default credentials
  • Session cookies work over HTTP
  • Meshtastic node connection and message exchange verified

Security Test:

  • Verifies Node IP address hidden from anonymous users in API responses
  • Verifies MQTT configuration hidden from anonymous users
  • Verifies Node IP address visible to authenticated users
  • Verifies MQTT configuration visible to authenticated users
  • Verifies protected endpoints require authentication

V1 API Test:

  • Tests v1 REST API endpoints with Bearer token authentication
  • Verifies Bearer token requests bypass CSRF protection
  • Verifies POST/PUT/DELETE work without CSRF token when using Bearer auth
  • Verifies session-based requests still require CSRF token

Reverse Proxy Test:

  • Production deployment with COOKIE_SECURE=true
  • HTTPS-ready configuration
  • Trust proxy enabled for reverse proxy compatibility
  • CORS configured for HTTPS domain
  • Meshtastic node connection and message exchange verified

Reverse Proxy + OIDC Test:

  • OIDC authentication integration
  • Mock OIDC provider health checks
  • Authorization flow and session creation
  • Hybrid mode (OIDC + local auth)
  • Meshtastic node connection verified

Virtual Node CLI Test:

  • Virtual Node Server enabled on TCP port 4404
  • Meshtastic Python client successfully connects
  • Node data download and synchronization verified
  • Test message sent on gauntlet channel (index 3)
  • Message delivery confirmed via Web UI API
  • Virtual Node Server connection logging verified

Backup & Restore Test:

  • System backup created from running dev container
  • New container spun up with RESTORE_FROM_BACKUP env var
  • Data integrity verified (node count, message count, settings)
  • Restore event logged in audit log
  • Dev container unaffected by restore test

Database Migration Test:

  • SQLite to PostgreSQL migration verified
  • SQLite to MySQL migration verified
  • Data integrity confirmed for both target databases
  • Row counts match between source and target

DB Backing Consistency Test:

  • SQLite, PostgreSQL, and MySQL backends tested with same device
  • Node counts within ±10 across all three backends
  • Favorite counts identical across all backends
  • Key station verified as favorite on all backends

Yeraze added a commit that referenced this pull request May 16, 2026
Bumps the version across all five canonical files (package.json,
package-lock.json, helm/meshmonitor/Chart.yaml,
desktop/src-tauri/tauri.conf.json, desktop/package.json) for the
4.5.2 release.

Headline: MeshCore channel support is the major feature in this
release.

- Phase 1 (#3034) — MeshCoreManager exposes listChannels / setChannel
  / deleteChannel and mirrors the device's channel list into the
  shared `channels` table on connect.
- Phase 2 (#3038) — MeshCoreChannelsView renders one tab per channel
  from the synced list; the send path is channel-idx aware.
- Phase 3 (#3039) — Configuration UI adds, edits, regenerates the
  secret of, and deletes channels on the device; PUT/DELETE
  /api/channels/:id route writes through to MeshCore via the correct
  registry.
- Polish (#3040) — empty-slot filter (the MAX_CHANNELS=40 leak),
  DB reconcile for stale empty rows, DM-view filters out channel
  pseudo-pubkeys and the local node from the peer sidebar.

Also includes the per-node position-precision fix (#3033) from this
release window.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant